<template>
  <div ref="boxRef" class="model bigBox">
    <!-- :style="{
    width:`${screenWidth}px`
  }" -->
    <!-- 头部 -->
    <div ref="headerRef" class="menu">
      <div class="last">ChatGPT Demo</div>
    </div>
    <!-- 聊天区域  -->
    <div
      ref="wrap"
      class="chat"
      :style="{
        height: `calc(100vh - ${headerHeight + footerHeight}px)`,
      }"
    >
      <ol ref="content" style="padding-left: 0">
        <li v-for="(str, index) in NewList" :key="index * Math.random()">
          <div :class="isMe(str.name) ? 'self' : 'other'">
            <div class="avatar">
              <img
                :src="
                  require(`@/assets/img/${
                    isMe(str.name) ? 'right' : 'chat'
                  }.jpg`)
                "
                draggable="false"
              />
            </div>
            <div :class="['msg', { msg_right: isMe(str.name) }]">
              <p
                v-if="index < NewList.length - 1"
                :class="isMe(str.name) ? 'right' : 'left'"
                v-html="parseMd(str.msg)"
              ></p>
              <template v-else>
                <p
                  ref="msg"
                  :class="isMe(str.name) ? 'right' : 'left'"
                  v-html="!isTyped ? parseMd(str.msg) : ''"
                ></p>
              </template>
            </div>
          </div>
        </li>
      </ol>
    </div>
    <!-- 底部发消息 -->
    <div ref="footerRef" class="footer">
      <textarea
        v-model="word"
        @keyup.enter="sendFn"
        class="textarea"
        ref="textarea"
        autocomplete="off"
        type="textarea"
        placeholder="请输入内容"
        @input="inputFangFn($event, 2000)"
      />
      <div class="emojis iconfont icon-fasong" @click="sendFn"></div>
    </div>
    <div></div>
  </div>
</template>

<script>
import Fingerprint2 from 'fingerprintjs2'
import { setList, getList } from '@/utils/information'
// 引入打印机插件
import Typed from 'typed.js'
import { useMarked } from '@/utils/useMarked'
import { fetchEventSource } from '@microsoft/fetch-event-source'
import { signRequest } from '@/utils/CryptoJS'

export default {
  name: 'My-Chat',
  data () {
    return {
      screenWidth: '',
      word: '', // 输入框的内容
      // 聊天最新数据
      NewList: [
        // 只根据 name 属性，即可判断出这个消息应该渲染到左侧还是右侧
        { name: 'me', msg: 'hi,你好!' },
        { name: 'xs', msg: '我是ChatGPT小助手' }
      ],
      // 机器人返回的时间列表
      timer: '',
      headerHeight: 0, // 头部的height
      footerHeight: 0, // 底部的height
      boxHeight: 0, // 整个盒子的height
      murmur: '', // 指纹
      isTyped: false,
      shijian: null
    }
  },
  computed: {
    screenClass () {
      if (this.screenWidth <= 500) {
        return 'mobile'
      } else {
        return 'desktop'
      }
    },
    // 计算  传入的name 值 是否等于 me  不等于直接return 出去
    isMe () {
      return (name = '') => name.toLocaleLowerCase() === 'me'
    },
    // 解析markdwon
    parseMd () {
      return (str) => useMarked(str)
    },
    historyMeChat () {
      return this.NewList.filter((el) => {
        return el.name === 'me'
      })
    }
  },
  created () {
    this.FingerprintFn()
  },
  mounted () {
    this.initDomHeight()
    this.textareaFn()
    // 拿到数据
    if (getList('NodeList') === null) {
      this.NewList = [
        { name: 'me', msg: 'hi,你好!' },
        { name: 'xs', msg: '我是ChatGPT小助手' }
      ]
    } else {
      this.NewList = JSON.parse(getList('NodeList'))
    }

    // 监听浏览器刷新事件
    window.addEventListener('beforeunload', (e) => this.beforeunloadFn(e))
    window.addEventListener('storage', (e) => this.storageFn(e))
    // resize事件是窗口大小改变时发生的事件
    window.addEventListener('resize', this.initDomHeight)
    // 跳转聊天最底下
    this.toBottom()
    this.keydownFn()
  },
  beforeDestroy () {
    // 销毁
    window.removeEventListener('resize', this.initDomHeight)
    window.removeEventListener('beforeunload', this.beforeunloadFn)
  },
  methods: {
    // 请求机器人数据
    async GetChat () {
      const that = this
      const fetchData = async () => {
        const data = JSON.stringify({
          content: this.word.replace('\n', ''),
          conversionId: this.murmur,
          stream: true
        })
        const { timestamp, nonce, signature } = signRequest(data)
        let originMessage = ''
        let receiveing = false
        // 分别将签名、密钥、时间戳 至请求头
        await fetchEventSource('http://gxcfly.top:8000/api/v1/chat/gpt3', {
        // await fetchEventSource('http://127.0.0.1:8000/api/v1/chat/gpt3', {
          method: 'POST',
          headers: {
            admin: 'admin',
            signature: signature,
            nonce: nonce,
            timestamp: timestamp,
            'Content-Type': 'application/json',
            Accept: 'text/event-stream'
          },
          body: data,
          onopen (res) {
            originMessage = ''
            console.log('Connection ')
          },
          onmessage (event) {
            const response = JSON.parse(event.data)
            let receiveMsg = response.data
            if (receiveMsg.includes('null') || receiveMsg.includes('data')) {
              receiveMsg = ' '
            }
            if (receiveing) {
              that.NewList.pop()
            }
            receiveing = true
            originMessage = originMessage + receiveMsg
            that.NewList.push({
              name: 'xs',
              msg: originMessage
            })
            const lastEl = that.$refs.msg[that.$refs.msg.length - 1]
            that.WatchMutationFn(lastEl)
            that.toBottom()
          },
          onclose () {
            originMessage = ''
            console.log('Connection closed by the server')
          },
          onerror (err) {
            originMessage = ''
            console.log('There was an error from server', err)
          }
        })
      }
      fetchData()
    },

    // 获取盒子的高度
    initDomHeight () {
      this.headerHeight = this.$refs.headerRef.offsetHeight
      this.footerHeight = this.$refs.footerRef.offsetHeight
      this.screenWidth = window.innerWidth
      this.boxHeight = this.$refs.boxRef.offsetHeight
    },
    // 让textarea高度自动撑宽
    textareaFn () {
      const textarea = this.$refs.textarea
      // console.log('textarea原先高度', textarea.offsetHeight)
      textarea.addEventListener('input', (e) => {
        textarea.style.height = textarea.offsetHeight
        textarea.style.height = e.target.scrollHeight + 'px'
      })
    },
    // 回车以及提交的事件
    sendFn () {
      // 兜底校验
      if (this.word.trim().length === 0) return
      this.NewList.push({
        msg: this.word,
        name: 'me'
      })
      this.GetChat()
      this.word = ''
      const lastEl = this.$refs.msg[this.$refs.msg.length - 1]
      this.WatchMutationFn(lastEl)
      this.toBottom()
    },
    // 设备指纹
    FingerprintFn () {
      const _that = this
      Fingerprint2.get(function (components) {
        const values = components.map(function (component, index) {
          if (index === 0) {
            // 把微信浏览器里UA的wifi或4G等网络替换成空,不然切换网络会ID不一样
            return component.value.replace(/\bNetType\/\w+\b/, '')
          }
          return component.value
        })
        // 生成最终id murmur
        // console.log(values) // 使用的浏览器信息
        const murmur = Fingerprint2.x64hash128(values.join(''), 31)
        // console.log(murmur) // 生成的标识码
        _that.murmur = murmur
      })
    },
    // 机器人回复消息
    handleResponse (msg) {
      this.isTyped = true
      this.NewList.push({
        name: 'xs',
        msg
      })
      this.timer = setTimeout(() => {
        this.$nextTick(() => {
          const options = {
            //  进行解析文件是否还有html css的文件
            strings: [useMarked(msg)],
            typeSpeed: 5,
            showCursor: false,
            // 打印机完成之后做的事
            onComplete: (self) => {
              // 然后这里去找到这个标识，修改 msg
              // 但因为肯定是末项，所以也可以不用标识，知识规范些是否用到都要有
              this.isTyped = false
              this.toBottom()
            }
          }

          // 拿到发消息的最后一项
          const lastEl = this.$refs.msg[this.$refs.msg.length - 1]
          // this.LastElement = lastEl
          // 打印机的实例
          const typed = new Typed(lastEl, options)
          this.WatchMutationFn(lastEl)

          this.toBottom()
        })
      }, 500)
    },
    // 实时检测打印机的高度
    WatchMutationFn (target) {
      const MutationObserver =
        window.MutationObserver ||
        window.webkitMutationObserver ||
        window.MozMutationObserver
      /**
       * MutationObserver DOM 发生变化时被调用。
       */
      let recordHeight = 0
      const _that = this
      const mutationObserver = new MutationObserver(function (mutations) {
        // console.log(mutations)

        // 返回一个 lastEl 的css 高度
        const height = window
          .getComputedStyle(target)
          .getPropertyValue('height')
        if (height === recordHeight) {
          return
        }
        recordHeight = height
        _that.$refs.wrap.scrollTop = Math.floor(
          Number(recordHeight.replace('px', '')) + _that.$refs.wrap.scrollTop
        )
      })

      mutationObserver.observe(target, {
        childList: true, // 子节点的变动（新增、删除或者更改）
        attributes: true, // 属性的变动
        characterData: true, // 节点内容或节点文本的变动
        subtree: true // 是否将观察器应用于该节点的所有后代节点
      })
    },
    // 键盘按下事件
    keydownFn () {
      let index = 0 // 初始化索引为0

      const NewStr = this.historyMeChat.reverse()
      const _that = this
      // 监听键盘事件
      document.addEventListener('keydown', function (event) {
        if (event.keyCode === 38) {
          // 上箭头
          index = index === 0 ? NewStr.length - 1 : index - 1
          // console.log('上箭头', index)
          // console.log(NewStr[index].msg) // 输出当前选中的项
          _that.word = NewStr[index].msg
        } else if (event.keyCode === 40) {
          // 下箭头
          index = index === NewStr.length - 1 ? 0 : index + 1
          // console.log('下箭头', index)
          // console.log(NewStr[index].msg) // 输出当前选中的项
          _that.word = NewStr[index].msg
        }
      })
    },
    // 封装一个发送消息底部自动跳过去
    toBottom () {
      this.$nextTick(() => {
        // 内容区的盒子的滚动条   =  装内容的盒子的高度  - 内容区的盒子的滚动条的滚动条
        this.$refs.wrap.scrollTop =
          this.$refs.content.offsetHeight - this.$refs.wrap.offsetTop
        // console.log('滚动条高度', this.$refs.wrap.scrollTop)
      })
    },
    // 浏览器的回调事件
    beforeunloadFn (e) {
      setList('NodeList', JSON.stringify(this.NewList))
      // console.log('刷新保存了数据', e)
    },
    storageFn (e) {
      this.NewList = [
        { name: 'me', msg: 'hi,你好!' },
        { name: 'xs', msg: '我是ChatGPT小助手' }
      ]
    },
    inputFangFn (e, delay) {
      clearTimeout(this.shijian)
      this.shijian = setTimeout(() => {
        // console.log(e.target.value)
      }, delay)
    }
  }
}
</script>

<style lang="less" scoped>
@import url("@/style/common.css");
.model {
  height: 100vh;
  &.bigBox {
    background: transparent;
  }
  .chat {
    overflow-x: hidden;
    background: none;
    overflow-y: scroll;
    height: 100vh;
    margin: 0;
    font-family: "Courier New", Courier, monospace;
    &::-webkit-scrollbar {
      width: 10px;
      height: 10px;
    }
  }
}
</style>
